package com.example.netty.handler;

import cn.hutool.core.exceptions.ExceptionUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;

import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    /**
     * 管理一个全局map，保存连接进服务端的通道数量
     */
    private static final ConcurrentHashMap<ChannelId, ChannelHandlerContext> CHANNEL_MAP = new ConcurrentHashMap<>();

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        try {
            InetSocketAddress inetSocketAddress = (InetSocketAddress) ctx.channel().remoteAddress();
            String clientIp = inetSocketAddress.getHostString();
            // 获取连接通道唯一标识
            ChannelId channelId = ctx.channel().id();
            // 如果map中不包含此连接，就保存连接
            if (CHANNEL_MAP.containsKey(channelId)) {
                log.info("Netty-Socket-客户端【{}】是连接状态，此时的连接通道数量: {}", channelId, CHANNEL_MAP.size());
            } else {
                //保存连接
                CHANNEL_MAP.put(channelId, ctx);
                log.info("Netty-Socket-客户端【{}】[{}:{}]连接netty服务器，此时的连接通道数量: {}", channelId, clientIp, inetSocketAddress.getPort(), CHANNEL_MAP.size());
            }
        } catch (Exception e) {
            log.error("channelActive error: {}", ExceptionUtil.getMessage(e));
        }
    }

    /**
     * 当通道有数据可读时执行
     *
     * @param ctx 上下文对象，可以从中取得相关联的 Pipeline、Channel、客户端地址等
     * @param msg 客户端发送的数据
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        log.info("接收Netty-Socket-客户端【{}】报文: {}", ctx.channel().id(), msg);
    }

    /**
     * 读取数据完毕
     *
     * @param ctx 上下文对象
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        log.info("Netty-Socket-客户端【{}】数据接收完毕", ctx.channel().id());

        String msg = "服务端收到消息了";
        channelWrite(ctx.channel().id(), msg);
        log.info("给Netty-Socket-客户端发送【{}】报文: {}", ctx.channel().id(), msg);
    }

    /**
     * 服务端给客户端发送消息
     *
     * @param channelId 连接通道唯一id
     * @param msg       需要发送的消息内容
     */
    private void channelWrite(ChannelId channelId, String msg) {
        try {
            ChannelHandlerContext ctx = CHANNEL_MAP.get(channelId);
            if (ctx == null) {
                log.info("Netty-Socket-通道【{}】不存在", channelId);
                return;
            }
            ByteBuf byteBuf = Unpooled.copiedBuffer(msg.getBytes(StandardCharsets.UTF_8));
            // 将客户端的信息直接返回写入ctx,同时进行刷新
            ctx.writeAndFlush(byteBuf);
        } catch (Exception e) {
            log.error("channelWrite error: {}", ExceptionUtil.getMessage(e));
        }
    }

    /**
     * 发生异常时执行
     *
     * @param ctx   上下文对象
     * @param cause 异常对象
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // 关闭于客户端的 Socket 连接
        ctx.channel().close();
        log.error("Netty-Socket【{}】发生了错误，此连接被关闭，此时连通数量: {}", ctx.channel().id(), CHANNEL_MAP.size());
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        try {
            InetSocketAddress inetSocketAddress = (InetSocketAddress) ctx.channel().remoteAddress();
            String clientIp = inetSocketAddress.getAddress().getHostAddress();
            ChannelId channelId = ctx.channel().id();
            // 包含此客户端才去删除
            if (CHANNEL_MAP.containsKey(channelId)) {
                // 删除连接
                CHANNEL_MAP.remove(channelId);
                log.info("Netty客户端【{}】退出Netty服务器[{}:{}]，此时的连接通道数量: {}", channelId, clientIp, inetSocketAddress.getPort(), CHANNEL_MAP.size());
            }
        } catch (Exception e) {
            log.error("channelInactive error: {}", ExceptionUtil.getMessage(e));
        }
    }
}
